/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/


#include "game_local.h"

/*-----------------------------------------------------------------------------
	Game main module :
-----------------------------------------------------------------------------*/

//
//	EGShip::EGShip
//
EGShip::EGShip( lua_State *L, int tidx )
{
	life_time	=	0;

	LUA_INTERFACE(L);
	EString		hxf_mesh_path	=	lua.RequireFieldString(tidx, "hxf_mesh");
	EString		vis_mesh_path	=	lua.RequireFieldString(tidx, "vis_mesh");
	EString		phys_mesh_path	=	lua.RequireFieldString(tidx, "phys_mesh");
	EString	engine_sound_path = lua.RequireFieldString(tidx, "engine_snd");

	hxf_mesh			=	ge()->LoadMeshFromFile( hxf_mesh_path	 )->Clone();
	vis_mesh			=	ge()->LoadMeshFromFile( vis_mesh_path	 )->Clone();
	phys_mesh			=	ge()->LoadMeshFromFile( phys_mesh_path	 )->Clone();
	
	ship_bbox			=	hxf_mesh->ComputeBBox();
	
	mass				=	lua.RequireFieldNumber(tidx, "mass");
	float cm_offset_x	=	lua.RequireFieldNumber(tidx, "cm_offset_x");
	float cm_offset_z	=	lua.RequireFieldNumber(tidx, "cm_offset_z");
	EMatrix	xform		=	EMatrix::Translate( cm_offset_x, 0, cm_offset_z );
	//xform.SetIdentity();
	hxf_mesh->Transform( xform );
	vis_mesh->Transform( xform );
	phys_mesh->Transform( xform );

	integral_density	=	lua.RequireFieldNumber(tidx, "integral_density");
	water_resistance	=	lua.RequireFieldNumber(tidx, "water_resistance");
	water_resistance2	=	lua.RequireFieldNumber(tidx, "water_resistance2");
	water_viscosity		=	lua.RequireFieldNumber(tidx, "water_viscosity");
	
	CONFIG_REGISTER_VAR( ship_show_grid,			false );
	CONFIG_REGISTER_VAR( ship_disable_simulation,	false );

	//
	//	create vis mesh :	
	//
	rend_model	=	rs()->GetFRScene()->AddEntity();
	rend_model->SetMesh( vis_mesh );

	//	
	//	create phys mesh :
	//
	phys_body	=	phys()->AddEntity();
	phys_body->SetDynamic();
	phys_body->SetMass( mass );
	phys_body->SetMesh( phys_mesh );
	phys_body->Spawn();


	gfx_vorts	=	rs()->GetFRScene()->AddGFX();
	gfx_vorts->AllocSprites( SHIP_MAX_VORTS );
	gfx_vorts->SetTexture( "textures/ship_vorts.tga", RS_GFX_NORMAL );

	// Sound stuff
	//engine_snd = ss()->CreateSoundSource(engine_sound_path.CStr());
	//engine_snd->SetMinMaxDistance(10.0f, 1000.0f);
	//engine_snd->SetVolume(1.0f);
	//engine_snd->Play(true);

	//
	//	Lighting :
	//	
	light	=	rs()->GetFRScene()->AddLight();
	light->SetEnable( true );
	//light->SetType( RS_LIGHT_SPOT_SHADOW );
	light->SetRadius( 200, 40 );
	light->SetColor( EColor(4,4,4,1) );

	//
	//	Screws :
	//	
#if 0
	IPxTriMesh	screwL	=	ge()->LoadMeshFromFile("scenes/uboat.esx|screwL");
	IPxTriMesh	screwR	=	ge()->LoadMeshFromFile("scenes/uboat.esx|screwR");
	rend_screwL			=	rs()->GetFRScene()->AddEntity();
	rend_screwR			=	rs()->GetFRScene()->AddEntity();
	rend_screwL->SetMesh( screwL );
	rend_screwR->SetMesh( screwR );
	screw_angle	=	0;
	
	UpdateScrews();
#endif	
}


//
//	EGShip::~EGShip
//
EGShip::~EGShip( void )
{

}


//
//	EGShip::UpdateScrews
//
void EGShip::UpdateScrews( void )
{
#if 0
	EPoint p;
	EQuaternion q;
	
	phys_body->GetPose(p, q);
	
	screw_angle += 0.1f;

	EMatrix	tL = EMatrix::RotateX(  screw_angle ) * EMatrix::Translate(-35.1f,  1.85f, 0.43f) * EMatrix::FromPose( p, q );
	EMatrix	tR = EMatrix::RotateX( -screw_angle ) * EMatrix::Translate(-35.1f, -1.85f, 0.43f) * EMatrix::FromPose( p, q );
	
	tL.ToPose( p, q );
	rend_screwL->SetPose( p, q );
	
	tR.ToPose( p, q );
	rend_screwR->SetPose( p, q );
#endif	
}


//
//	EGShip::SetPose
//
void EGShip::SetPose( const EPoint &pos, const EQuaternion &orient )
{
	phys_body->SetPose( pos, orient );
	rend_model->SetPose( pos, orient );
	
	light->SetPose( pos + EVector(0,0,100), EQuaternion() );
}


//
//	EGShip::GetPose
//
void EGShip::GetPose( EPoint &pos, EQuaternion &orient )
{
	phys_body->GetPose( pos, orient );
}


//
//	EGShip::UpdateForces
//
void EGShip::UpdateForces( void )
{
	EVector f, t;
	phys_body->GetTotalForces( f, t );
	phys_body->AddForce( -f );
	phys_body->AddTorque( -t );
	
	UpdateHXFGrid();
	UpdateHXF( EGame::self->waving );
}


//
//	EGShip::Update
//
void EGShip::Update( float dtime )
{
	life_time += dtime;

	EPoint		p;
	EQuaternion q;

	UpdateForces();	
	
	phys_body->GetPose( p, q );
	rend_model->SetPose( p, q );
	UpdateScrews();

	EPoint		old_pos;
	EQuaternion	old_orient;

	#if 0
	engine_snd->GetPose( old_pos, old_orient );
	engine_snd->SetPose( p, q );
	#endif
	//engine_snd->SetVelocity((p - old_pos)/dtime);

	EPoint			l_pos;
	EQuaternion		l_orient;
	EFrustum		l_frust;
	rs()->GetFRScene()->GetViewProj(l_frust, l_pos, l_orient);

	//EVec4 l_up	 = EVec4(0.0f, 0.0f, 1.0f, 0.0f);
	//EVec4 l_view = EVec4(1.0f, 0.0f, 0.0f, 0.0f);

	//LOGF("AHtung : %f %f %f", l_pos.x, l_pos.y, l_pos.z );
	light->SetPose( p + EVector(0,0,100), EQuaternion() );
	
	DrawVorticies(dtime, EGame::self->waving );
}


//
//	EGShip::ApplyForces
//
void EGShip::ApplyForces( const EVector &force, const EPoint &point )
{
	phys_body->AddForceAtLocalPos( force, point );
}



/*-----------------------------------------------------------------------------
	HXF GRID stuff :
-----------------------------------------------------------------------------*/

static uint GetNumPoints( float num ) {
	if (num<=1) {
		return 1;
	} else {
		return num;
	}
	return 1;
}


//
//	EShip::UpdateHXFSEGrid
//
void EGShip::UpdateHXFGrid( float density/* =0 */ )
{
	hxfgrid.grid.reserve( hxfgrid.grid.size() + 1 );
	hxfgrid.grid.clear();

	if (density<=0) {
		density = integral_density;
	}

	double	total_surface_area = 0;	
	double	total_surface_area2 = 0;	
	
	IPxTriMesh	mesh	=	hxf_mesh;

	//
	//	build grid :
	//
	for (uint i=0; i<mesh->GetTriangleNum(); i++) {
	
		uint i0, i1, i2;
		mesh->GetTriangle(i, i0, i1, i2);
		
		EPoint	v0	=	mesh->GetVertex(i0).position;
		EPoint	v1	=	mesh->GetVertex(i1).position;
		EPoint	v2	=	mesh->GetVertex(i2).position;
		EVector	v01	=	v1 - v0;	//	u
		EVector	v02	=	v2 - v0;	//	v
		

		EVector	n	=	mesh->TriangleNormal(i);
		double	s	=	mesh->TriangleArea(i);
		total_surface_area += s;
		
		uint elem_num = GetNumPoints(s * density);
		if (elem_num<1) {
			elem_num = 1;
		}
		
		double	ds = s / elem_num;
		
		for (uint j=0; j<elem_num; j++) {
			SurfElem	se;
			
			float u=0, v=0;
			do {
				//u	=	EMath::Randf();
				//v	=	EMath::Randf();
				u	=	mtrand.randomMTf();
				v	=	mtrand.randomMTf();
			} while (u+v>1);
			
			total_surface_area2 += ds;
			
			se.position	=	v0 + v01 * u + v02 * v;
			se.normal	=	n;
			se.area		=	ds;
			hxfgrid.grid.push_back(se);
		} 
	}

	//
	//	balance grid :
	//
	uint n = hxfgrid.grid.size();
	for (uint i=0; i<n; i++) {
		swap(hxfgrid.grid[i], hxfgrid.grid[rand()%n]);
	}
}


//
//	EGShip::UpdateHXF
//
void EGShip::UpdateHXF( HardRef<EGWaving> waving )
{
	const float WATER_DENSITY	=	1000;
	
	//for (float x=-100; x<100; x+=5) {
	//	for (float y=-100; y<100; y+=10) {
	//		float	p	=	0;
	//		float	z	=	0;
	//		EVector	v;	
	//		rs()->GetFRScene()->GetWaterDynamics( EPoint(x,y,0), p, z, v );
	//		rs()->GetDVScene()->DrawPoint( EPoint(x,y,z), 1.4, EColor(1,1,0.5,1) );
	//	}
	//}

	if (!waving) {
		LOG_WARNING("Waving is not setup");
		return;
	}

	EPoint p;
	EQuaternion q;
	EMatrix t;
	
	phys_body->GetPose(p, q);
	t = EMatrix::FromPose( p, q );
	
	float num_pnts[3] = {0,0,0};
	
	for (uint i=0; i<hxfgrid.grid.size(); i++) {
	
		SurfElem	se		= hxfgrid.grid[i];
		EVector		normal	= se.normal;
		
		EVector		vse		= phys_body->GetLocalPointVelocity( se.position );	//	local point velocity

		//	transform surface element to world space :		
		se.position	=	se.position.Transform( t );
		se.normal	=	se.normal.Transform	 ( t );

		float	pr			=	0;
		float	surflevel	=	0;
		EVector	wave_vel	=	EVector(0,0,0);
		rs()->GetFRScene()->GetWaterDynamics( se.position, pr, surflevel, wave_vel );
		EVector		vwtr	=	wave_vel;
		EVector		vel		=	vse - vwtr;
		float		vel_p2	=	vel.LengthSqr();								//	squared point velocity relative to water particles
		EVector		vdir	=	vel.Normalize();

		//rs()->GetDVScene()->DrawPoint( EVec4(se.position.x, se.position.y, surflevel, 1), 0.1, EVec4(1,1,1,1) );
		//rs()->GetDVScene()->DrawPoint( EVec4(se.position.x, se.position.y, se.position.z, 1), 0.1, EVec4(1,1,1,1) );
		//rs()->GetDVScene()->DrawArrow( se.position, vwtr, 1.0, EVec4(0,1,1,1) );
		
		float	s	=	se.area;

		float	factor	=	pr > 0 ? 1 : 0;
		
		//	compute dynamic pressure :
		float	v_dot_n	=	EMath::Dot( vdir, se.normal );
		//float	fds		=	water_resistance * vel_p2 / 2 * abs(v_dot_n) * se.area * WATER_DENSITY * factor;
		float	fds		=	0;
				fds		+=	( water_resistance2 * vel_p2       * abs(v_dot_n) );
				//fds		+=	( water_resistance  * sqrt(vel_p2) * abs(v_dot_n) );
				//fds		+=	( water_viscosity   * sqrt(vel_p2) * (1 - abs(v_dot_n)) );
				fds		*=	( se.area * WATER_DENSITY * factor );
		EVector	fd		=	- (vdir * fds);
		
		float	lf		=	clamp<float>(pr / (1), 0, 1);
		EColor	color	=	EMath::Lerp( EColor(1,0,0,1), EColor(1,1,0,1), lf );
		//rs()->GetDVScene()->DrawPoint( se.position, 0.1, color );
		//rs()->GetDVScene()->DrawArrow( se.position, fd, 0.001, EColor::kRed );
		
		
		if (factor<0.5) {
			hxfgrid.grid[i].submerged = false;
		} else {
			hxfgrid.grid[i].submerged = true;
		}

		EVector	f	=	- (se.normal * (pr * s));

		phys_body->AddForceAtPos( f + fd, se.position );

		//	Visual stuff :
		//vorts:;
		float wave_height = surflevel;
		
		if ( se.position.z > wave_height-0.4 && se.position.z < wave_height+0.4 ) {
		
			float	sign	=	EMath::Sign( fd.z );
			float	drop	=	sqrt( fd.Length() ) * sign;
			rs()->GetFRScene()->MakeWaterDrop( EPoint(se.position.x, se.position.y, 0,1), -drop * 0.001f );
		
			float lifetime	=	2;//clamp<float>(vel_p2, 1, 4);
			float rx = EMath::Randf(-0.3f,0.3f);
			float ry = EMath::Randf(-0.3f,0.3f);
			float lf = EMath::Randf(lifetime/2, lifetime);
		
			AddVorticies( se.position.x, se.position.y, wave_height, lf, (se.normal.x+rx) * 6,  (se.normal.y+ry) * 6, se.area );
		}
	}
	

	//
	//	damp wrong
	//
	EVector	force, torque;	
	phys_body->GetTotalForces( force, torque );
}


//
//	EGShip::DrawVorticies
//
void EGShip::DrawVorticies( float dtime, HardRef<EGWaving> waving )
{
	for (uint i=0; i<SHIP_MAX_VORTS; i++) {
		Vortex *v = &vorticies[i];

		gfx_vorts->SetVisible( i, !v->dead );
		
		if (v->dead) {
			continue;
		}
		
		EPoint	pos[4];
		ETexCoord	uv0[4];
		float	f	=	v->lifetime / v->total_lifetime;
		float	r	=	0.7 + 3.0 * (f);
		
		uv0[0]		=	ETexCoord(0,0);
		uv0[1]		=	ETexCoord(0,1);
		uv0[2]		=	ETexCoord(1,1);
		uv0[3]		=	ETexCoord(1,0);
		pos[0]		=	EPoint(0,0,0);
		pos[1]		=	EPoint(0,0,0);
		pos[2]		=	EPoint(0,0,0);
		pos[3]		=	EPoint(0,0,0);
		
		float a	=	1-f;
		//a = 1-f;
		float	pr			=	0;
		float	surflevel	=	0;
		EVector	wave_vel	=	EVector(0,0,0);
		rs()->GetFRScene()->GetWaterDynamics( v->pos, pr, surflevel, wave_vel );
		
		v->pos.z	=	surflevel;
		
		gfx_vorts->SetVertices( i, pos, uv0, uv0 );
		gfx_vorts->AimToCamera( i, v->pos, r, v->angle, 1 );
		gfx_vorts->SetColor( i, EColor(1,1,1, a*0.7) );
	}

	//
	//	update :
	//	
	for (uint i=0; i<SHIP_MAX_VORTS; i++) {
		Vortex *v = &vorticies[i];

		if (v->lifetime > v->total_lifetime) {
			v->dead = true;
		}

		v->pos		+=	(v->vel * dtime);
		v->lifetime +=	dtime;
	}
}


//
//	EGShip::AddVorticies
//
void EGShip::AddVorticies( float x, float y, float z, float total_lifetime, float vx, float vy, float r )
{
	return;

	for (uint i=0; i<SHIP_MAX_VORTS; i++) {
		Vortex *v = &vorticies[i];
		
		if (v->dead) {
			v->angle			=	EMath::Randf(0, 2*PI);
			v->dead				=	false;
			v->lifetime			=	0;
			v->total_lifetime	=	total_lifetime;
			v->pos.x			=	x;
			v->pos.y			=	y;
			v->pos.z			=	z;
			v->pos.w			=	1;
			v->vel.x			=	vx;
			v->vel.y			=	vy;
			v->vel.z			=	0;
			v->vel.w			=	0;
			v->radius			=	r;
			return;
		}
	}
}


//
//
//
EGShip::ShipDynamics EGShip::GetShipDynamics( void )
{
	ShipDynamics	sd;
	phys_body->GetTotalForces( sd.force, sd.torque );
	GetPose( sd.position, sd.orient );
	
	sd.time			=	life_time;
	sd.ship_mass	=	phys_body->GetMass();
	sd.ship_length	=	ship_bbox.Size().x;
	sd.ship_width	=	ship_bbox.Size().y;
	
		float	pr			=	0;
		float	surflevel	=	0;
		EVector	wave_vel	=	EVector(0,0,0);
		rs()->GetFRScene()->GetWaterDynamics( sd.position, pr, surflevel, wave_vel );

	sd.wave_height	=	surflevel;

	return sd;
}